package my.mall.admin.api.service

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper
import com.baomidou.mybatisplus.core.metadata.IPage
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import my.mall.admin.api.pojo.goods.GoodsDetail
import my.mall.admin.api.pojo.goods.GoodsItem
import my.mall.admin.api.pojo.goods.GoodsStatusParam
import my.mall.admin.api.pojo.goods.detail.AttrInfo
import my.mall.admin.api.pojo.goods.detail.SkuInfo
import my.mall.admin.api.pojo.goods.detail.SpecInfo
import my.mall.admin.api.pojo.login.LoginInfo
import my.mall.common.annotation.Slf4j
import my.mall.common.annotation.Slf4j.Companion.log
import my.mall.common.bean.JsonParser
import my.mall.common.enums.RespMetaEnum
import my.mall.common.exception.WebBaseException
import my.mall.common.pojo.CommonResp
import my.mall.common.pojo.PageParam
import my.mall.common.pojo.PageResp
import my.mall.common.util.BigDecimalUtils
import my.mall.common.util.DateUtils
import my.mall.common.util.FutureUtils
import my.mall.common.util.RespUtils
import my.mall.db.entity.Goods
import my.mall.db.entity.GoodsCategory
import my.mall.db.entity.GoodsExt
import my.mall.db.entity.GoodsSku
import my.mall.db.mapper.GoodsCategoryMapper
import my.mall.db.mapper.GoodsExtMapper
import my.mall.db.mapper.GoodsMapper
import my.mall.db.mapper.GoodsSkuMapper
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.util.CollectionUtils
import org.springframework.util.StringUtils
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ThreadPoolExecutor
import java.util.stream.Collectors

@Slf4j
@Service
open class GoodsService(
    private val goodsMapper: GoodsMapper,
    private val goodsCategoryMapper: GoodsCategoryMapper,
    private val goodsExtMapper: GoodsExtMapper,
    private val goodsSkuMapper: GoodsSkuMapper,
    private val jsonParser: JsonParser,
    private val executor: ThreadPoolExecutor
) {

    open fun getGoodsInfoList(loginInfo: LoginInfo, goodsStatus: Int?, page: PageParam): CommonResp<PageResp<GoodsItem>> {
        var iPage: IPage<Goods> = Page(page.pageNum, page.pageSize)
        iPage = goodsMapper.selectPage(
            iPage, QueryWrapper<Goods>()
                .select(
                    Goods.ID,
                    Goods.GOODS_NAME,
                    Goods.CREATED_TIME,
                    Goods.MAIN_IMG_URL,
                    Goods.GOODS_PRICE,
                    Goods.REAL_SALES_VOLUME,
                    Goods.CATEGORY_ID1,
                    Goods.GOODS_STATUS,
                    Goods.GOODS_ORDER,
                )
                .eq(Goods.STORE_ID, loginInfo.storeId)
                .eq(goodsStatus != null, Goods.GOODS_STATUS, goodsStatus)
                .orderByDesc(Goods.ID)
        )
        val goodsList = iPage.records
        if (CollectionUtils.isEmpty(goodsList)) {
            return RespUtils.success(page.pageNum, page.pageSize, 0, emptyList())
        }
        val cateMap = goodsCategoryMapper.selectList(
            QueryWrapper<GoodsCategory>()
                .select(
                    GoodsCategory.ID,
                    GoodsCategory.CATEGORY_NAME,
                )
                .`in`(GoodsCategory.ID, goodsList.stream().map { v: Goods? -> v!!.categoryId1 }.collect(Collectors.toList()))
        ).stream().collect(Collectors.toMap(GoodsCategory::id) { v: GoodsCategory? -> v })
        val goodsInfoList: MutableList<GoodsItem> = ArrayList(goodsList.size)
        for (goods in goodsList) {
            val goodsInfo = GoodsItem()
            goodsInfo.id = goods.id
            goodsInfo.goodsName = goods.goodsName
            goodsInfo.createdTime = DateUtils.formatLocalDateTime(goods.createdTime)
            goodsInfo.mainImgUrl = goods.mainImgUrl
            goodsInfo.goodsPrice = BigDecimalUtils.fenToYuan(goods.goodsPrice)
            goodsInfo.realSalesValue = goods.realSalesVolume!!
            val category = cateMap[goods.categoryId1]
            goodsInfo.firstCateName = category?.categoryName
            goodsInfo.goodsStatus = goods.goodsStatus!!
            goodsInfo.goodsOrder = goods.goodsOrder!!
            goodsInfoList.add(goodsInfo)
        }
        return RespUtils.success(iPage.current, iPage.size, iPage.total, goodsInfoList)
    }

    open fun getGoodsDetail(storeId: Long?, goodsId: Long?): GoodsDetail {
        val goodsDetail = GoodsDetail()
        val goods = goodsMapper.selectOne(
            QueryWrapper<Goods>()
                .select(
                    Goods.GOODS_NAME,
                    Goods.SELLING_POINT,
                    Goods.MAIN_IMG_URL,
                    Goods.GOODS_PRICE,
                    Goods.INIT_SALES_VOLUME,
                    Goods.REAL_SALES_VOLUME,
                )
                .eq(Goods.ID, goodsId)
                .eq(Goods.STORE_ID, storeId)
        ) ?: return goodsDetail
        val goodsExtFuture = CompletableFuture.runAsync({
            val goodsExt = goodsExtMapper.selectOne(
                QueryWrapper<GoodsExt>()
                    .select(
                        GoodsExt.MAIN_IMG_URLS,
                        GoodsExt.CONTENT_IMG_URLS,
                        GoodsExt.ATTR_DETAIL,
                    )
                    .eq(GoodsExt.GOODS_ID, goodsId)
            )
            if (StringUtils.hasLength(goodsExt.mainImgUrls)) {
                goodsDetail.mainImgUrls = jsonParser.parseJsonToList(goodsExt.mainImgUrls, String::class.java)
            }
            if (StringUtils.hasLength(goodsExt.contentImgUrls)) {
                goodsDetail.contentImgUrls = jsonParser.parseJsonToList(goodsExt.contentImgUrls, String::class.java)
            }
            if (StringUtils.hasLength(goodsExt.attrDetail)) {
                goodsDetail.attrList = jsonParser.parseJsonToList(goodsExt.attrDetail, AttrInfo::class.java)
            }
        }, executor)
        val goodsSkuFuture = CompletableFuture.runAsync({
            val goodsSkuList = goodsSkuMapper.selectList(
                QueryWrapper<GoodsSku>()
                    .select(
                        GoodsSku.ID,
                        GoodsSku.STOCK_NUM,
                        GoodsSku.SKU_PRICE,
                        GoodsSku.SPEC_IDS,
                        GoodsSku.SPEC_DETAIL,
                        GoodsSku.SKU_IMG_URL,
                    )
                    .eq(GoodsSku.GOODS_ID, goodsId)
            )
            val specMap: MutableMap<String, SpecInfo> = HashMap()
            val skuInfoList: MutableList<SkuInfo> = ArrayList(goodsSkuList.size)
            for (sku in goodsSkuList) {
                val specDetail = sku.specDetail
                val specValue = StringBuilder()
                val attrList = jsonParser.parseJsonToJsonNode(specDetail)
                for (attr in attrList) {
                    val attrName = attr["attrName"].asText()
                    var specInfo = specMap[attrName]
                    if (specInfo == null) {
                        specInfo = SpecInfo(attrName, mutableListOf())
                        specMap[attrName] = specInfo
                    }
                    val itemId = attr["itemId"].asLong()
                    val itemName = attr["itemName"].asText()
                    val specItem = SpecInfo.SpecItem(itemId, itemName)
                    if (!specInfo.attrValueList.contains(specItem)) {
                        specInfo.attrValueList.add(specItem)
                    }
                    specValue.append(attrName).append(":").append(itemName).append("-")
                }
                val skuInfo = SkuInfo()
                skuInfo.id = sku.id
                skuInfo.stockNum = sku.stockNum!!
                skuInfo.skuPrice = BigDecimalUtils.fenToYuan(sku.skuPrice!!)
                skuInfo.specIds = sku.specIds!!
                skuInfo.specValue = specValue.substring(0, specValue.length - 1)
                skuInfo.skuImgUrl = sku.skuImgUrl
                skuInfoList.add(skuInfo)
            }
            goodsDetail.skuList = skuInfoList
            goodsDetail.specList = ArrayList(specMap.values)
        }, executor)
        val categoryFuture = CompletableFuture.runAsync {
            val cateMap = goodsCategoryMapper.selectList(
                QueryWrapper<GoodsCategory>()
                    .select(
                        GoodsCategory.ID,
                        GoodsCategory.CATEGORY_NAME,
                    )
                    .`in`(GoodsCategory.ID, goods.categoryId1, goods.categoryId2, goods.categoryId3)
            ).stream().collect(Collectors.toMap(GoodsCategory::id, GoodsCategory::categoryName))
            goodsDetail.categoryName1 = cateMap[goods.categoryId1]
            goodsDetail.categoryName2 = cateMap[goods.categoryId2]
            goodsDetail.categoryName3 = cateMap[goods.categoryId3]
        }
        goodsDetail.id = goodsId
        goodsDetail.goodsName = goods.goodsName
        goodsDetail.sellingPoint = goods.sellingPoint
        goodsDetail.goodsPrice = BigDecimalUtils.fenToYuan(goods.goodsPrice!!)
        goodsDetail.categoryId3 = goods.categoryId3
        FutureUtils.waitAllTasks(goodsExtFuture, goodsSkuFuture, categoryFuture)
        return goodsDetail
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun saveGoods(loginInfo: LoginInfo, goodsDetail: GoodsDetail) {
        val goods = Goods()
        goods.storeId = loginInfo.storeId
        goods.adminId = loginInfo.adminId
        goods.goodsStatus = 1.toByte()
        val categoryId2 = goodsCategoryMapper.selectOne(KtQueryWrapper(GoodsCategory::class.java)
            .select(GoodsCategory::parentId)
            .eq(GoodsCategory::id, goodsDetail.categoryId3)
        ).parentId ?: throw WebBaseException(400, "商品分类不存在")
        val categoryId1 = goodsCategoryMapper.selectOne(KtQueryWrapper(GoodsCategory::class.java)
            .select(GoodsCategory::parentId)
            .eq(GoodsCategory::id, categoryId2)
        ).parentId ?: throw WebBaseException(400, "商品分类不存在")
        goods.categoryId1 = categoryId2
        goods.categoryId2 = categoryId1
        goods.categoryId3 = goodsDetail.categoryId3
        setGoodsInfo(goods, goodsDetail)
        goodsMapper.insert(goods)
        val goodsExt = GoodsExt()
        goodsExt.goodsId = goods.id
        setGoodsExt(goodsExt, goodsDetail)
        goodsExtMapper.insert(goodsExt)
        for (skuInfo in goodsDetail.skuList) {
            val goodsSku = GoodsSku()
            goodsSku.goodsId = goods.id
            setSkuInfo(goodsDetail, skuInfo, goodsSku)
            goodsSkuMapper.insert(goodsSku)
        }
    }

    @Transactional(rollbackFor = [Exception::class])
    open fun editGoods(loginInfo: LoginInfo, goodsDetail: GoodsDetail): CommonResp<*> {
        // 上架商品不能编辑
        val goods = goodsMapper.selectOne(
            QueryWrapper<Goods>()
                .select(
                    Goods.GOODS_NAME,
                    Goods.SELLING_POINT,
                    Goods.MAIN_IMG_URL,
                    Goods.GOODS_PRICE,
                    Goods.INIT_SALES_VOLUME,
                    Goods.REAL_SALES_VOLUME,
                    Goods.CATEGORY_ID1,
                    Goods.CATEGORY_ID2,
                    Goods.CATEGORY_ID3,
                )
                .eq(Goods.ID, goodsDetail.id)
        ) ?: return RespUtils.warning<Any>(RespMetaEnum.PARAM_ERROR)
        if (1 == goods.goodsStatus!!.toInt()) {
            return RespUtils.warning(400, "上架商品不能修改")
        }
        if (loginInfo.storeId != goods.storeId) {
            return RespUtils.warning(400, "不能夸店铺修改商品")
        }
        goods.storeId = loginInfo.storeId
        setGoodsInfo(goods, goodsDetail)
        goodsMapper.updateById(goods)
        val goodsExt = goodsExtMapper.selectOne(
            QueryWrapper<GoodsExt>()
                .select(
                    GoodsExt.MAIN_IMG_URLS,
                    GoodsExt.CONTENT_IMG_URLS,
                    GoodsExt.ATTR_DETAIL,
                )
                .eq(GoodsExt.GOODS_ID, goods.id)
        )
        setGoodsExt(goodsExt, goodsDetail)
        goodsExtMapper.update(
            goodsExt, QueryWrapper<GoodsExt>()
                .eq(GoodsExt.GOODS_ID, goods.id)
        )
        val skuMap: MutableMap<Long?, GoodsSku?> = goodsSkuMapper.selectList(
            QueryWrapper<GoodsSku>()
                .select(
                    GoodsSku.ID,
                    GoodsSku.STOCK_NUM,
                    GoodsSku.SKU_PRICE,
                    GoodsSku.SPEC_IDS,
                    GoodsSku.SPEC_DETAIL,
                    GoodsSku.SKU_IMG_URL,
                )
                .eq(GoodsSku.GOODS_ID, goods.id)
        ).stream().collect(Collectors.toMap(GoodsSku::id, { v: GoodsSku? -> v }))
        for (skuInfo in goodsDetail.skuList) {
            var goodsSku: GoodsSku? = null
            if (skuInfo.id != null) {
                goodsSku = skuMap.remove(skuInfo.id)
            }
            if (goodsSku == null) {
                goodsSku = GoodsSku()
                goodsSku.goodsId = goods.id
                setSkuInfo(goodsDetail, skuInfo, goodsSku)
                goodsSkuMapper.insert(goodsSku)
            } else {
                setSkuInfo(goodsDetail, skuInfo, goodsSku)
                goodsSkuMapper.updateById(goodsSku)
            }
        }
        if (!CollectionUtils.isEmpty(skuMap)) {
            goodsSkuMapper.deleteBatchIds(ArrayList(skuMap.keys))
        }
        return RespUtils.success()
    }

    private fun setGoodsInfo(goods: Goods, goodsDetail: GoodsDetail?) {
        goods.goodsName = goodsDetail!!.goodsName
        goods.sellingPoint = goodsDetail.sellingPoint
        goods.mainImgUrl = goodsDetail.mainImgUrls[0]
        goods.goodsPrice = BigDecimalUtils.yuanToFen(goodsDetail.goodsPrice)
        goods.initSalesVolume = goodsDetail.initSalesVolume
    }

    private fun setGoodsExt(goodsExt: GoodsExt, goodsDetail: GoodsDetail?) {
        goodsExt.mainImgUrls = jsonParser.formatObjToJson(goodsDetail!!.mainImgUrls)
        if (!CollectionUtils.isEmpty(goodsDetail.contentImgUrls)) {
            goodsExt.contentImgUrls = jsonParser.formatObjToJson(goodsDetail.contentImgUrls)
        }
        if (!CollectionUtils.isEmpty(goodsDetail.attrList)) {
            goodsExt.attrDetail = jsonParser.formatObjToJson(goodsDetail.attrList)
        }
    }

    private fun setSkuInfo(goodsDetail: GoodsDetail, skuInfo: SkuInfo, goodsSku: GoodsSku) {
        val specList = goodsDetail.specList
        val skuDetailList: MutableList<Map<String, Any>> = ArrayList(specList.size)
        try {
            val ids = skuInfo.specIds.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            for (i in specList.indices) {
                val specInfo = specList[i]
                val id = ids[i]
                val skuDetail: MutableMap<String, Any> = HashMap(3)
                skuDetail["attrName"] = specInfo.attrName
                for (specItem in specInfo.attrValueList) {
                    if (id == specItem.itemId.toString()) {
                        skuDetail["itemId"] = specItem.itemId
                        skuDetail["itemName"] = specItem.itemName
                        break
                    }
                }
                skuDetailList.add(skuDetail)
            }
        } catch (e: Throwable) {
            log.warn("parsed skuDetail failed!", e)
            throw WebBaseException(400, "规格参数错误")
        }
        goodsSku.specIds = skuInfo.specIds
        goodsSku.specDetail = jsonParser.formatObjToJson(skuDetailList)
        goodsSku.skuImgUrl = skuInfo.skuImgUrl
        goodsSku.stockNum = skuInfo.stockNum
        goodsSku.skuPrice = BigDecimalUtils.yuanToFen(skuInfo.skuPrice)
    }

    open fun updateStatus(loginInfo: LoginInfo, statusParam: GoodsStatusParam) {
        val goods = goodsMapper.selectOne(
            QueryWrapper<Goods>()
                .select(
                    Goods.ID,
                    Goods.GOODS_STATUS,
                )
                .eq(Goods.ID, statusParam.goodsId)
                .eq(Goods.STORE_ID, loginInfo.storeId)
        ) ?: throw WebBaseException(RespMetaEnum.PARAM_ERROR)
        if (goods.goodsStatus != null && goods.goodsStatus == statusParam.status) {
            throw WebBaseException(RespMetaEnum.PARAM_ERROR)
        }
        goodsMapper.update(
            UpdateWrapper<Goods>()
                .set(Goods.GOODS_STATUS, statusParam.status)
                .eq(Goods.ID, statusParam.goodsId)
                .eq(Goods.STORE_ID, loginInfo.storeId)
        )
    }
}
